﻿using System;
using System.Collections.Generic;
using System.Globalization;

using Microsoft.Practices.EnterpriseLibrary.Logging;

namespace TfsSpam.Utilities
{
    /// <summary>
    /// The TfsLogger class provides logging capabilities
    /// based on the Enterprise Library logging block.
    /// </summary>
    public class TfsLogger
    {
        #region -- Private enums
        /// <summary>
        /// The Category enumeration defines the levels of logging
        /// that the logger supports.
        /// </summary>
        [Flags]
        private enum Category
        {
            /// <summary>
            /// Trace-level information.
            /// </summary>
            Verbose = 2,

            /// <summary>
            /// Useful, but non-critical information.
            /// </summary>
            Information = 8,

            /// <summary>
            /// A warning for something that's not quite an error.
            /// </summary>
            Warning = 32,

            /// <summary>
            /// An error during processing, but the process can go on.
            /// </summary>
            Error = 128,

            /// <summary>
            /// A critical error that will abort processing.
            /// </summary>
            Critical = 512
        }
        #endregion

        #region -- Public logging methods
        /// <summary>
        /// Logs a critical-level event. Typically this would be a fatal application or system exception.
        /// </summary>
        /// <param name="message">Message body to log</param>
        /// <param name="args">An <see cref="object"/> array containing zero or more objects to format</param>
        public void Critical( string message, params object[] args )
        {
            Write( Category.Critical, message, args );
        }

        /// <summary>
        /// Logs an error-level event. Typically this would be a non-fatal application or system exception.
        /// </summary>
        /// <param name="message">Message body to log</param>
        /// <param name="args">An <see cref="object"/> array containing zero or more objects to format</param>
        public void Error( string message, params object[] args )
        {
            Write( Category.Error, message, args );
        }
        /// <summary>
        /// Logs a warning-level event. Typically this would be an unexpected or undesired application
        /// or system state that does not cause an exception.
        /// </summary>
        /// <param name="format">A <see cref="string"/> containing zero or more format items</param>
        /// <param name="args">An <see cref="object"/> array containing zero or more objects to format</param>
        public void Warning( string format, params object[] args )
        {
            Write( Category.Warning, format, args );
        }

        /// <summary>
        /// Logs an information-level event. Typically this would be information that is not necessarily
        /// unexpected or undesirable, but that may help with testing, debugging and diagnostics.
        /// </summary>
        /// <param name="format">A <see cref="string"/> containing zero or more format items</param>
        /// <param name="args">An <see cref="object"/> array containing zero or more objects to format</param>
        public void Information( string format, params object[] args )
        {
            Write( Category.Information, format, args );
        }

        /// <summary>
        /// Logs a verbose-level event. Typically this would be information that falls within
        /// the bounds of normal program flow.
        /// </summary>
        /// <param name="format">A <see cref="string"/> containing zero or more format items</param>
        /// <param name="args">An <see cref="object"/> array containing zero or more objects to format</param>
        public void Verbose( string format, params object[] args )
        {
            Write( Category.Verbose, format, args );
        }
        #endregion

        #region -- Protected, virtual, methods
        /// <summary>
        /// The AddContext method can be overridden by derived logger
        /// classes to add context to the messages being logged.
        /// </summary>
        protected virtual void AddContext()
        {
        }
        #endregion

        #region -- Private methods
        /// <summary>
        /// Writes a message to the registered log listeners.
        /// </summary>
        /// <param name="categories">The categories defined for the message.</param>
        /// <param name="format">The format string for the message.</param>
        /// <param name="args">Optional arguments to add to the format string.</param>
        private void Write( Category categories, string format, params object[] args )
        {
            if ( format == null )
            {
                return;
            }

            // Let derived classes add any context they desire.
            AddContext();

            LogEntry logEntry = new LogEntry();
            logEntry.Categories = BuildCategoriesCollection( categories );

            if ( Logger.ShouldLog( logEntry ) )
            {
                logEntry.Message = string.Format( CultureInfo.CurrentCulture, format, args );
                Logger.Write( logEntry );
            }
        }

        /// <summary>
        /// Creates a <see cref="ICollection&lt;T&gt;"/> of <see cref="string"/>s representing the
        /// <see cref="Category"/> flag values that make up selectedCategories.
        /// </summary>
        /// <param name="selectedCategories"><see cref="Category"/> flag values to inspect</param>
        /// <returns><see cref="ICollection&lt;T&gt;"/> of selected category names</returns>
        private static ICollection<string> BuildCategoriesCollection( Category selectedCategories )
        {
            Array allCategoryValues = Enum.GetValues( typeof( Category ) );
            List<string> collection = new List<string>( allCategoryValues.Length );

            foreach ( Category category in allCategoryValues )
            {
                if ( ( selectedCategories & category ) == category )
                {
                    collection.Add( category.ToString() );
                }
            }

            return collection;
        }
        #endregion
    }
}
